package io.github.chutian0610.sqlparser.token

import arrow.core.Either
import java.util.*


/** SQL Token enumeration */
sealed class Token {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        return when (other) {
            is Token -> other == this
            is TokenWithLocation -> other.token == this
            else -> false
        }
    }

    override fun hashCode(): Int {
        return javaClass.hashCode()
    }

    /* An end-of-file marker, not a real token */
    object EOF : Token() {
        override fun toString(): String {
            return "EOF"
        }
    }

    /* keyword */
    data class KeyWord(val value: KeyWordEnum) : Token() {
        override fun toString(): String {
            return "KeyWord($value)"
        }
    }

    /* optionally quoted identifier */
    data class Identifier(val value: String, val quote: CodePoint?) : Token() {
        override fun toString(): String {
            return if (quote != null) {
                "Identifier(${quote.toUnicodeChar()}$value${quote.toUnicodeChar()})"
            } else {
                "Identifier($value)"
            }
        }
    }

    data class Placeholder(val value: String) : Token() {
        override fun toString(): String {
            return "Placeholder(?$value)"
        }
    }

    /* Literal */
    data class Literal(val value: LiteralEnum) : Token() {
        override fun toString(): String {
            return "Literal($value)"
        }
    }

    /* Punctuation & Operator */
    data class Symbol(val value: SymbolEnum) : Token() {
        override fun toString(): String {
            return "Symbol($value)"
        }
    }

    /* WhiteSpace */
    data class WhiteSpace(val value: WhiteSpaceEnum) : Token() {
        override fun toString(): String {
            return "WhiteSpace($value)"
        }
    }

    /* IllegalToken */
    data class IllegalChar(val text: String) : Token() {
        override fun toString(): String {
            return "IllegalChar($text)"
        }
    }

    companion object {
        /**
         * word is identifier or keyword
         */
        fun isWord(token: Token): Boolean = token is KeyWord || token is Identifier

        /**
         * word is identifier or keyword
         */
        fun isWord(token: TokenWithLocation): Boolean = isWord(token.token)

        fun makeKeyWord(s: String): Token {
            return makeWord(s, null)
        }

        fun makeWord(s: String, quote: CodePoint?): Token {
            val upper = s.uppercase(Locale.getDefault())
            val find = KeyWordEnum.fromUpperText(upper)
            return if (find != null) {
                // find matched keyword, return keyword
                KeyWord(find)
            } else {
                // no matched keyword, return Identifier
                Identifier(s, quote)
            }
        }

        fun matchingIdentifierEndQuote(ch: CodePoint): Either<TokenizerError, CodePoint> {
            return when (ch) {
                CodePoint('"'.code) -> Either.Right(CodePoint('"'.code)) // ANSI and most dialects
                CodePoint('['.code) -> Either.Right(CodePoint(']'.code)) // MS SQL
                CodePoint('`'.code) -> Either.Right(CodePoint('`'.code)) // MySQL
                else -> Either.Left(TokenizerError("unexpected quoting style: $ch"))
            }
        }
    }
}